package cs3220;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class PreAssembler {
	
	private BufferedReader asmIn;
	private BufferedWriter preOut;
	private Map<String,String> labels;
	private String currentAddr, startAddr;
	private String fileName;
	
	/**
	 * Sets up the pre assembler.
	 * @param fileName Name of the .asm file (without path or exception).
	 * Example: for C:\Test5.asm the fileName will be "Test5" 
	 */
	public PreAssembler(String fileName) {
		this.fileName = fileName;
	}
	
	/**
	 * Pre assemble the .asm file to a .pre file
	 */
	public void run() {
		try {
			asmIn = new BufferedReader(new FileReader(fileName+".asm"));
			System.out.println("Opened assembly file: "+fileName+".asm.");
			
			preOut = new BufferedWriter(new FileWriter(fileName+".pre"));
			System.out.println("Created temporary file: "+fileName+".pre.");
					
			labels = new HashMap<String,String>();
			
			String toProcess;
			while(asmIn.ready()) {
				toProcess = asmIn.readLine();
				toProcess = remComments(toProcess);
				
				if(toProcess.length() > 1) {
					if(toProcess.toUpperCase().contains(".ORIG")) {
						handleOrig(toProcess);
					} else if(toProcess.contains(":")) { //it's a label
						handleLabel(toProcess);
					} else if((toProcess = handleInstr(toProcess)) != null){
						preOut.write(toProcess+"\n");
					}
				}
			}
			preOut.flush();
			asmIn.close();
			preOut.close();
			System.out.println("Pre-Processed assembly file: "+fileName+".asm to pre file: "+fileName+".pre.");
		} catch (IOException e) {
			System.err.println("Problem reading assembly file or outputting pre file.");
		}
	}
	
	
	/**
	 * Removes comments and cleans up spaces/tabs
	 * @param toProcess unprocessed string
	 * @return cleaner string to be assembled
	 */
	private String remComments(String toProcess) {
		String[] tokens;
		toProcess = toProcess.replaceAll("(\\s)+", "\t"); //replaces spaces with a single tab char
		if(toProcess.contains(";")) { //chop off comment
			tokens = toProcess.split(";");
			if(tokens.length > 0)
				toProcess = tokens[0];
		}
		return toProcess;
	}

	public String incrementAddr(String curr) {
		int val = Integer.parseInt(curr, 16);
		val++;
		return Integer.toHexString(val);
	}
	
	
	public String hexStrSizer(int size, String curr) {
		int len = curr.length();
		
		if(len > size) { //concat LSB
			return curr.substring(len-size);
		}
		
		char toPadd = '0';
		//String first = ""+curr.charAt(0); //allows for signed padding
		//if(first.matches("[89A-Fa-f]"))
		//	toPadd = 'F';
		
		
		String toRet = "";
		for(int i = 0; i < size-len; i++ ) {
			toRet+=toPadd;
		}
		toRet += curr;
		return toRet;
	}
	
	private void handleLabel(String toProcess) {
		String[] tokens;
		tokens = toProcess.split(":");
		System.out.println("Putting label: "+tokens[0]+" at currAddr: "+currentAddr);
		labels.put(tokens[0], currentAddr);
	}

	private void handleOrig(String toProcess) {
		String[] tokens;
		tokens = toProcess.split("\t");
		int tmp;
		if(tokens[2].contains("0x")) {
			tokens[2] = tokens[2].substring(2);
			tmp = Integer.parseInt(tokens[2],16);
		} else {
			tmp = Integer.parseInt(tokens[2]);
		}
		
		tmp /= 4;
		tokens[2] = Integer.toHexString(tmp);
		currentAddr = startAddr = tokens[2];
	}
	
	private String handleInstr(String toProcess) {
		String toRet = null;
		if (!toProcess.matches("((\\s)*[^A-Za-z0-9]*)*")) { //only space characters
			toRet = hexStrSizer(8,currentAddr)+":"+toProcess.substring(1);
			currentAddr = incrementAddr(currentAddr);
		}
		return toRet;
	}

	/**
	 * 
	 * @param label
	 * @return the address of the label if it exists
	 */
	public String getLabel(String label) {
		return labels.get(label);
	}
	
	public String getStartAddr() {
		return startAddr;
	}

	
}
